package game.targets.room;

import game.targets.Constants;
import game.targets.Locator;
import game.targets.RayCallback;
import game.targets.entity.Entity;
import game.targets.entity.target.Target;
import game.targets.entity.world.Bow;
import game.targets.entity.world.Player;
import game.targets.entity.world.Wall;
import game.targets.observer.Event;
import game.targets.observer.Observer;
import game.targets.observer.Subject;
import game.targets.round.Round;
import game.targets.screen.RoomScreen;

import java.util.ArrayList;

import com.badlogic.gdx.graphics.PerspectiveCamera;
import com.badlogic.gdx.graphics.g3d.Environment;
import com.badlogic.gdx.graphics.g3d.ModelBatch;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.math.Vector3;

public class Room implements Observer, Constants {

	public static float MAX_CHARGE_TIME = 1.2f;
	public static float MAX_HOLD_TIME = 0.5f + MAX_CHARGE_TIME;
	public static float MAX_TARGET_WALL_WIDTH = 10;
	public static float TARGET_WALL_POSITION_X = -5;
	public static float TARGET_WALL_POSITION_Y = -6;
	public static float MAX_TARGET_WALL_HEIGHT = 10;
	public static float DISTANCE_TO_TARGET_WALL = -20;
	public static float DISTANCE_TO_BOTTOM_WALL = -15;
	public static float DISTANCE_TO_SIDE_WALL = -10;
	public static float WALL_WIDTH = 16;
	public static float WALL_HEIGHT = 8;

	public static float MAX_BOW_VIEW_HEIGHT_DEGREES = (float) Math.abs(Math.atan(DISTANCE_TO_TARGET_WALL
			/ (MAX_TARGET_WALL_HEIGHT + TARGET_WALL_POSITION_Y))
			* MathUtils.radiansToDegrees);
	public static float MAX_BOW_VIEW_WIDTH_DEGREES = (float) Math.abs(Math.atan((MAX_TARGET_WALL_WIDTH + TARGET_WALL_POSITION_X)
			/ DISTANCE_TO_TARGET_WALL)
			* MathUtils.radiansToDegrees);

	private PerspectiveCamera camera;
	private ArrayList<Entity> entities;
	private ArrayList<Target> targets;
	private Environment environment;
	private Player player;
	private ArrayList<Entity> removed;
	private float timer;
	private Subject subject;
	private boolean charging;
	private float chargeTimer;
	private Round round;
	private int roundNumber;
	private float xRotation;
	private float yRotation;
	private RayCallback rayCallback;
	private RoomScreen screen;

	public Room(RoomScreen screen, PerspectiveCamera camera, Environment environment, RayCallback rayCallback) {
		this.camera = camera;
		this.screen=screen;
		this.environment = environment;
		charging = false;
		timer = 20;
		subject = new Subject();
		subject.addObserver(this);
		subject.addObserver(Locator.getSounds());
		this.rayCallback = rayCallback;

		entities = new ArrayList<Entity>();
		removed = new ArrayList<Entity>();
		targets = new ArrayList<Target>();
		xRotation = 0;
		yRotation = 0;

		// let assets load
		while (!Locator.getAssets().getManager().update()) {
		}
		roundNumber = 1;
		round = new Round(roundNumber, entities, subject);
		createRound();
		player = new Player(new Vector3(0, 0, 0), camera, subject);
		entities.add(player);
		// bottom wall
		Wall wall = new Wall(new Vector3(0, -WALL_HEIGHT, 0), new Vector3(0, 0, 0), new Vector3(1, 0, 0), new Vector3(DISTANCE_TO_TARGET_WALL, 0.5f,
				DISTANCE_TO_SIDE_WALL), subject);
		entities.add(wall);
		// front wall
		wall = new Wall(new Vector3(DISTANCE_TO_TARGET_WALL, 0, 0), new Vector3(0, 0, 1), new Vector3(0, 1, 0), new Vector3(WALL_HEIGHT, 1,
				-DISTANCE_TO_SIDE_WALL), subject);
		entities.add(wall);
		// left wall
		wall = new Wall(new Vector3(0, 0, -DISTANCE_TO_SIDE_WALL), new Vector3(1, 0, 0), new Vector3(0, 0, 0), new Vector3(-DISTANCE_TO_TARGET_WALL,
				0.5f, WALL_HEIGHT), subject);
		entities.add(wall);
		// right wall
		wall = new Wall(new Vector3(0, 0, DISTANCE_TO_SIDE_WALL), new Vector3(1, 0, 0), new Vector3(0, 0, 0), new Vector3(-DISTANCE_TO_TARGET_WALL,
				0.5f, WALL_HEIGHT), subject);
		entities.add(wall);
		// top wall
		wall = new Wall(new Vector3(0, WALL_HEIGHT, 0), new Vector3(0, 0, 0), new Vector3(1, 0, 0), new Vector3(-DISTANCE_TO_TARGET_WALL, 0.5f,
				-DISTANCE_TO_SIDE_WALL), subject);
		entities.add(wall);
	}

	public void update(float delta) {
		if (charging) {
			chargeTimer += delta;
			if (chargeTimer > MAX_CHARGE_TIME) {
				// hold arrow at max pull before resetting charge back to 0
				if (chargeTimer > MAX_HOLD_TIME) {
					chargeTimer = 0;
					player.charge(camera.direction);
				} else {
					player.hold();
				}
			}
		}

		// update all entities
		for (Entity e : entities) {
			if (e.isExists()) {
				e.update(delta);
			} else {
				removed.add(e);
			}
		}

		// delete objects
		for (Entity e : removed) {
			e.delete();
		}
		entities.removeAll(removed);
		targets.removeAll(removed);
		removed.clear();

		// update the round
		round.update(delta);

		timer -= delta;
		if (timer < 0) {
			subject.notify(null, Event.EVENT_OUT_OF_TIME);
		}
	}

	public void render(ModelBatch batch) {
		for (Entity e : entities) {
			e.render(batch, environment);
		}

	}

	public void fireArrow() {
		player.fireArrow(rayCallback.gethitPointWorld(), Math.min(MAX_CHARGE_TIME, chargeTimer));
		entities.add(player.getBow().getArrow());
	}

	private void createRound() {
		round.createRound();
		targets = round.getTargets();
		entities.addAll(targets);
	}

	public void endGame() {
		// delete objects
		for (Entity e : entities) {
			e.delete();
		}
		screen.endGame();
	}

	@Override
	public void onNotify(Entity entity, Event event) {
		System.out.println(event.name());
		switch (event) {
		case EVENT_TARGET_NORMAL_HIT:
			Locator.getStats().increaseTargetsHit();
			Locator.getStats().increaseScore(1);
			break;
		case EVENT_TARGET_BAD_HIT:
			Locator.getStats().increaseScore(BAD_POINTS);
			Locator.getStats().increaseTargetsHit();
			Locator.getStats().increaseBadHit();
			round.tempTargetHit((Target) entity);
			break;
		case EVENT_TARGET_TIME_HIT:
			timer += TIME_INCREASE;
			Locator.getStats().increaseTargetsHit();
			round.tempTargetHit((Target) entity);
			break;
		case EVENT_TARGET_BONUS_HIT:
			Locator.getStats().increaseBonusHit();
			Locator.getStats().increaseTargetsHit();
			Locator.getStats().increaseScore(BONUS_POINTS);
			break;
		case EVENT_CHARGING_SHOT:
			charging = true;
			player.charge(camera.direction);
			break;
		case EVENT_RELEASE_SHOT:
			charging = false;
			fireArrow();
			Locator.getStats().increaseArrowsFired();
			chargeTimer = 0;
			break;
		case EVENT_OUT_OF_TIME:
			endGame();
			break;
		default:
			break;
		}
	}

	public float getTimer() {
		return timer;
	}

	public void setTimer(float timer) {
		this.timer = timer;
	}

	public boolean isCharging() {
		return charging;
	}

	public void setCharging(boolean charging) {
		this.charging = charging;
	}

	public float getChargeTimer() {
		return chargeTimer;
	}

	public void setChargeTimer(float chargeTimer) {
		this.chargeTimer = chargeTimer;
	}

	public ArrayList<Target> getTargets() {
		return targets;
	}

	public void setTargets(ArrayList<Target> targets) {
		this.targets = targets;
	}

	public Subject getSubject() {
		return subject;
	}

	public void setSubject(Subject subject) {
		this.subject = subject;
	}

	public float getxRotation() {
		return xRotation;
	}

	public void setxRotation(float xRotation) {
		this.xRotation += xRotation;
	}

	public float getyRotation() {
		return yRotation;
	}

	public void setyRotation(float yRotation) {
		this.yRotation += yRotation;
	}

	public Bow getBow() {
		return player.getBow();
	}
}
